
#include "Merger.h"
#include "vector_dinamico.h"
#include "Termino.h"
#include "heap.h"
#include "FrontCoder.h"
#include "GeneradorIndice.h"
#include "Dato.h"

#include <stdio.h>
#include <stdbool.h>

#define TAMVT 10

/* Funcion utilizada por el heap para comparar dos terminos. */
int comparar_heap_lexico (const void *a, const void *b) {
	return Termino_Comparar ((Termino_t*) b, (Termino_t*) a);
}

int comparar_heap_datos (const void *a, const void *b) {
	return Dato_Comparar ((Dato_t*) b, (Dato_t*) a);
}

/* Funcion wrapper para destruir elementos. */
void destruir_elemento (void *e) {
	Termino_destruir ((Termino_t*) e);
}

void destruir_elemento_datos (void* e){
	Dato_destruir ( (Dato_t*) e );	
}

/* Funcion wrapper para cerrar archivos. */
void quemar_archivos (void* dato) {
	fclose ( (FILE*) dato );
}

/* Se crea un vector por cada letra, en el que habra almacenado los 
 * punteros a file de los archivos correspondientes a esa letra. */
void crear_matrices (vector_dinamico_t* abecedario, vector_dinamico_t* abecedario_datos) {
	
	for (int j = 0; j < 36; j++) {
		vector_dinamico_t* letra = vector_crear (30, quemar_archivos);
		vector_dinamico_t* letra_dato = vector_crear (30, quemar_archivos);
			
		vector_guardar (abecedario, j, letra);
		vector_guardar (abecedario_datos, j, letra_dato);	
	}
}

/* Se crean los punteros a file de cada letra y se guardan en la matriz, si falla alguna apertura devuelve false. */
bool abrir_archivos (vector_dinamico_t* abecedario, vector_dinamico_t* abecedario_datos, vector_dinamico_t* rutas) {
	for (int i = 0; i < vector_cantidad (rutas) ; i++) {
		Indice_t* documento = vector_obtener (rutas, i);
		vector_dinamico_t* lexico = Indice_verRutasLexico (documento);
		vector_dinamico_t* dato = Indice_verRutasDatos (documento);
		
		for (int j = 0; j < 36; j++) {
			char* direccion_letra = vector_obtener (lexico, j);
			char* direccion_dato = vector_obtener (dato, j);
			
			FILE* letra_archivo = fopen (direccion_letra, "rb");
			if (!letra_archivo) return false;
				
			FILE* datos_archivo = fopen (direccion_dato, "rb");
			if (!datos_archivo) {
				fclose (letra_archivo);
				return false;
			}
			
			vector_dinamico_t* letra = vector_obtener (abecedario, j);
			vector_dinamico_t* letra_dato = vector_obtener (abecedario_datos, j);
			
			int ultimaPos = vector_cantidad (letra);
			vector_guardar (letra, ultimaPos, letra_archivo);
			vector_guardar (letra_dato, ultimaPos, datos_archivo);
			
			if ((vector_cantidad(letra)) == (vector_tamanio(letra))) {
				vector_redimensionar (letra, (vector_cantidad (letra)) * 2);
				vector_redimensionar (letra_dato, (vector_cantidad (letra_dato)) * 2);
			}
		}
	}
	return true;
} 



bool todosLosArchivosEstanAlFinal(vector_dinamico_t* abecedario){
	
	bool finalizo = false;
	int cant = 0;
	
	for (int j = 0; j < vector_cantidad (abecedario); j++) {
		if (feof (vector_obtener(abecedario,j) ) ) cant++;		
	}
		
	if (cant == vector_cantidad(abecedario) ) finalizo = true; 
	return finalizo;
}

bool cargar_Vector_Terminos(vector_dinamico_t* vTermino , heap_t* heap){
		
		Termino_t* termino;
		Termino_t* terminoTope;
		int pos;
		
		if( heap_esta_vacio(heap) ) {
			vector_destruir(vTermino); 
			return false;
		}
		
		termino=heap_desencolar(heap);
				
		pos = vector_cantidad(vTermino);
		vector_guardar(vTermino, pos, termino);
		terminoTope = heap_ver_max (heap);
		
		while ( Termino_Comparar(termino,terminoTope) == 0 ){
			
			pos = vector_cantidad(vTermino);
			vector_guardar(vTermino, pos, heap_desencolar(heap));
			
			if ((vector_cantidad(vTermino)) == (vector_tamanio(vTermino))) {
				vector_redimensionar (vTermino, (vector_cantidad (vTermino)) * 2);
			}
			
			terminoTope= heap_ver_max (heap);
		
		}
		return true;

}





/* Lee un termino del fd. */
Termino_t* leer_lexico( FILE* fd, vector_dinamico_t* vCadenaAnterior, int i){
	
	char* cadenaEntera;
	char* cadenaAnterior;
	Termino_t* termino = NULL;
	
	int* repetidos = malloc(sizeof(int));
	if (!repetidos) return NULL;
	
	int* distintos = malloc(sizeof(int));
	if (!distintos) {
		free (repetidos);
		return NULL;
	}
	
	int* offset = malloc(sizeof(int));
	if (!distintos) {
		free (repetidos);
		free (distintos);
		return NULL;
	}
	
	if (!feof (fd) ) {
						
		*repetidos = -1;
		// repetidos
		fread (repetidos, sizeof(int), 1, fd);
		if ( (*repetidos) == -1 ) { 
			free(repetidos);
			free(distintos);
			free(offset);
			return NULL;
		
		}
		// distintos
		fread (distintos, sizeof(int), 1, fd);
				
		// cadena
		int largo = *distintos;
		char* cadena = malloc ( sizeof(char) * (largo+1) );
		fread (cadena, sizeof(char), largo, fd); 
		cadena[largo] = '\0';
		
		// offset
		fread (offset, sizeof(int), 1, fd);
		
		cadenaAnterior = vector_obtener (vCadenaAnterior, i);		
		cadenaEntera = obtener_cadena_entera (*distintos, *repetidos, cadenaAnterior, cadena);
		
		vector_guardar (vCadenaAnterior, i, cadenaEntera);
		
		termino = Termino_crear(cadenaEntera,i);
		free(cadena);
	}
	
	free (distintos);
	free (repetidos);
	free (offset);
	return termino;
}

bool Merge_escribir_termino (FILE* fd_lexico_destino, char* terminoAnterior, char* termino, int offsetSiguiente) {
	
	int* num = malloc (sizeof(int));
	if (!num) return false;
			
	/* Repetidos */
	*num = obtener_repetidos (termino, terminoAnterior);
	fwrite ( (const void*) num, sizeof (int), 1, fd_lexico_destino );
	
	/* Distintos */
	*num = obtener_distintos (termino, terminoAnterior);
	fwrite ( (const void*) num, sizeof (int), 1, fd_lexico_destino );
	
	/* Caracteres */
	char* truncado = obtener_truncado (termino, terminoAnterior);
	fwrite ( (const void*) truncado, sizeof (char), strlen (truncado), fd_lexico_destino );
	
	free(truncado);	
	
	/* Offset */
	*num = offsetSiguiente;
	fwrite ( (const void*) num, sizeof (int), 1, fd_lexico_destino );
	
	free (num);
	
	return true;
}

int leer_datos ( FILE* fd ){
	
	int numeroLeido;
	int* numero = malloc ( sizeof (int) );	
	if (!numero) return -1;		
	if (!feof (fd) ) {			
		*numero = -1;		
		fread ( numero , sizeof(int), 1, fd);		
		if ( (*numero) == -1 ) { 
			free (numero);
			return -1;		
		}				
	}
	numeroLeido = *numero;
	free ( numero);
	return numeroLeido;	
}


bool escribir_destino (FILE* fd , int numero) {
	
	int* num = malloc (sizeof(int));
	if (!num) return false;			
	*num = numero;
	fwrite ( (const void*) num, sizeof (int), 1, fd );	
	free (num);
	return true;

}


heap_t* cargar_heap_datos ( vector_dinamico_t* vTermino, vector_dinamico_t* abecedario_datos, int* cantArchivoTotal ){
		
	*cantArchivoTotal = 0;
	int offset, cantArchivo = 0;
	int posAbsoluta;
	int posicion;
	Termino_t* termino;
	heap_t* heap = heap_crear (comparar_heap_datos);	
	for ( int i=0; i < vector_cantidad (vTermino) ; i++){
		
		termino = vector_obtener (vTermino , i);
		offset = Termino_ObtenerOffset (termino);
		cantArchivo = leer_datos ( vector_obtener (abecedario_datos, offset ) );
		*cantArchivoTotal = *cantArchivoTotal + cantArchivo;
		posAbsoluta = 0;		
		for( int j=0; j < cantArchivo ; j++){
			posicion= leer_datos ( vector_obtener (abecedario_datos, offset ) );		
			posAbsoluta = posAbsoluta + posicion;			
			Dato_t* dato = Dato_crear ( posAbsoluta, offset );
			heap_encolar (heap, dato );		
		}
	}			
	return heap;	
}

vector_dinamico_t* vaciar_datos_heap (FILE* fd, heap_t* heap, int* offsetDestino){
	
	vector_dinamico_t* vec_offset = vector_crear (TAMVT ,free);
	Dato_t* dato;	
	int i = 0, pos, posAnterior = 0, posAGuardar;
		
	while ( !heap_esta_vacio (heap) ){	
			
		dato = heap_desencolar (heap);
		pos = Dato_ObtenerPosicion (dato);
		posAGuardar = pos - posAnterior; //Distancias
		escribir_destino ( fd , posAGuardar ); 
		*offsetDestino = *offsetDestino + 1;
		posAnterior = pos;		
		
		if ( (vector_cantidad (vec_offset) ) == (vector_tamanio(vec_offset) ) ) 
				vector_redimensionar (vec_offset , (vector_cantidad (vec_offset)) * 2);
				
		int* offset = malloc (sizeof(int));
		*offset = Dato_ObtenerOffset (dato);		
		vector_guardar (vec_offset , i , offset);				
		Dato_destruir(dato);	
		i++;
	}
	return vec_offset;
}

int Merge_escribir_datos (FILE* destino, vector_dinamico_t* vTermino, vector_dinamico_t* abecedario_datos, int* offsetDestino ) {
	
	int* cantArchivoTotal = malloc ( sizeof (int) );	
	bool estado = true;
	vector_dinamico_t* vOffset;
	int offset;
	heap_t* heapDatos;
	int Cantpos ,pos; 
	int* posArchivo;	
	
	heapDatos = cargar_heap_datos (vTermino, abecedario_datos, cantArchivoTotal);	
	estado = escribir_destino ( destino, *cantArchivoTotal);		
	if(!estado){
		heap_destruir (heapDatos , destruir_elemento_datos );
		free(cantArchivoTotal);
	}	
	*offsetDestino = *offsetDestino + 1; 	
	vOffset = vaciar_datos_heap (destino , heapDatos, offsetDestino);
		
	for (int r=0 ; r < vector_cantidad (vOffset); r++){
		posArchivo = vector_obtener (vOffset , r );
		FILE* fd = vector_obtener (abecedario_datos , *posArchivo); 
		Cantpos = leer_datos (fd);
		escribir_destino( destino, Cantpos);
		*offsetDestino = *offsetDestino + 1;		
		for ( int j=0 ; j < Cantpos ; j++ ){							
			pos = leer_datos ( vector_obtener( abecedario_datos , *posArchivo ));
			escribir_destino (destino, pos);	
			*offsetDestino = *offsetDestino + 1;
		}		
	}
	offset = *offsetDestino;
	heap_destruir (heapDatos , destruir_elemento_datos );
	free (cantArchivoTotal) ;	
	vector_destruir( vOffset);	
	return offset * 4;
}



char* actualizar_termino (Termino_t* termino) {
	char* ntermino = Termino_obtenerNombre (termino);
	char* terminoAnterior = malloc (sizeof (char*) * strlen (ntermino) + 1);
	
	return strcpy (terminoAnterior, ntermino);
}

void cargar_heap_lexico (heap_t* heap , vector_dinamico_t* abecedario , vector_dinamico_t* vCadenaAnterior ){
	FILE* fd;
	Termino_t* termino;
	for ( int i=0; i < vector_cantidad(abecedario) ; i++) {
		fd = vector_obtener (abecedario,i);
		termino = leer_lexico (fd, vCadenaAnterior, i ) ;
		if(termino)
			heap_encolar (heap,termino);					
	} 	
}

void actualizar_heap_lexico (heap_t* heap, vector_dinamico_t* vTermino, vector_dinamico_t* vCadenaAnterior, vector_dinamico_t* abecedario){
	Termino_t* termino;
	FILE* fd;
	int narchivo;
	for ( int k = 0; k < vector_cantidad (vTermino); k++) {
		
		termino = vector_obtener (vTermino, k);
		narchivo = Termino_ObtenerOffset (termino);
		fd = vector_obtener ( abecedario ,narchivo );
		termino = leer_lexico (fd, vCadenaAnterior, narchivo);
		if( termino != NULL ) heap_encolar (heap,termino);				
	}

}

Indice_t* mergear (vector_dinamico_t* abecedario, vector_dinamico_t* abecedario_datos, char* lexico_destino, char* datos_destino) {
	
	Termino_t* termino;
	char* terminoAnterior = NULL;
	bool finalizo = false;
	int* offsetDestino = malloc (sizeof (int) );
	FILE* fd_lexico_destino = fopen (lexico_destino, "wb");
	FILE* fd_datos_destino = fopen (datos_destino, "wb");
	
	vector_dinamico_t* vCadenaAnterior = vector_crear( vector_cantidad (abecedario), free);
	if (!vCadenaAnterior) return NULL;	
	*offsetDestino = 0;
	heap_t* heap = heap_crear(comparar_heap_lexico);
	if (!heap) 	{
		free (offsetDestino);
		vector_destruir (vCadenaAnterior);
		return NULL;
	}	
	finalizo = todosLosArchivosEstanAlFinal ( abecedario );
	if (!finalizo) 
		cargar_heap_lexico (heap, abecedario, vCadenaAnterior );
		
	int offsetSiguiente = 0;
		
	while (!finalizo) {
		
		vector_dinamico_t* vTermino = vector_crear ( TAMVT , destruir_elemento);		
		if (!vTermino) {
			heap_destruir (heap, destruir_elemento);
			vector_destruir (vCadenaAnterior);
			return NULL;
		}		
		if(!cargar_Vector_Terminos ( vTermino ,heap)) break;
				
		termino = vector_obtener (vTermino, 0);
		
		Merge_escribir_termino (fd_lexico_destino, terminoAnterior, Termino_obtenerNombre (termino), offsetSiguiente);
		free (terminoAnterior);
		terminoAnterior = actualizar_termino (termino);
		offsetSiguiente = Merge_escribir_datos (fd_datos_destino, vTermino, abecedario_datos, offsetDestino );
		actualizar_heap_lexico ( heap, vTermino, vCadenaAnterior, abecedario);
	
		vector_destruir(vTermino);		
		finalizo = todosLosArchivosEstanAlFinal ( abecedario );
	}
		 	
	vector_destruir (vCadenaAnterior);
	heap_destruir (heap, destruir_elemento);
	free (terminoAnterior);
	free (offsetDestino);
	fclose (fd_lexico_destino);
	fclose (fd_datos_destino);
	
	return NULL;
} 



/* Funcion wrapper para destruir los vectores almacenados en el vector. */
void quemar_vector (void* dato) {
	vector_destruir ( (vector_dinamico_t*) dato);
}

vector_dinamico_t* crear_rutas (char* tipo, char* nombre) {
	
	char alphanum[] = NOMBRES_ARCH;
	vector_dinamico_t* vector = vector_crear(CANT_ARCH,free);
	if (!vector) return NULL;	
	/* Todas las letras */
	for (int i = 0; i < CANT_ARCH; i++) {		
		/* Construyo el string que sera el nombre del archivo */
		char* direccion = crear_string_direccion (alphanum[i], tipo, nombre, ".dat");
		if (!direccion) {
			vector_destruir (vector);
			return NULL;
		}
	vector_guardar (vector, i, direccion);	
	}
	return vector;
}


/* Funcion que se encarga de mergear todos los archivos recibidos en rutas. */
Indice_t* Merger_realizarMerge (vector_dinamico_t* rutas, char* nombre){
	//    vector dinamico ( vector_A, vector_B,....vector_Z,   vector_9) 36 posiciones	
	
	vector_dinamico_t* abecedario = vector_crear (CANT_ARCH, quemar_vector);
	vector_dinamico_t* abecedario_datos = vector_crear (CANT_ARCH, quemar_vector);
	
	// Creo la matriz de vectores.
	crear_matrices (abecedario, abecedario_datos);
	
	// Lleno la matriz. Si falla alguna apertura de un archivo, libero memoria y devuelvo NULL.
	if (!abrir_archivos (abecedario, abecedario_datos, rutas) ) {
		vector_destruir (abecedario);
		vector_destruir (abecedario_datos);	
		return NULL;
	}
	
	//creo los archivos donde se almacenara el mergeado
	vector_dinamico_t* lexicos_destino = crear_rutas ("_lexico_", nombre);
	vector_dinamico_t* datos_destino = crear_rutas ("_datos_", nombre);
	
	// Mergea por cada docuento, para la prueba solo mergeamos la a.
	for ( int i = 0 ; i <  CANT_ARCH ; i ++ ) {
		vector_dinamico_t* caracter_lexicos_fuente = vector_obtener (abecedario, i);
		vector_dinamico_t* caracter_datos_fuente = vector_obtener (abecedario_datos, i);
		char* lex_destino = vector_obtener (lexicos_destino, i);
		char* dat_destino = vector_obtener (datos_destino, i);
		mergear (caracter_lexicos_fuente, caracter_datos_fuente, lex_destino, dat_destino );
	}
	
	// mprimi_matriz (abecedario, abecedario_datos);
	
	vector_destruir (abecedario);
	vector_destruir (abecedario_datos);
	return (Indice_crear (lexicos_destino,datos_destino));
}
